package us.wili.dev.common.util.httpclient;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.CharSet;
import org.apache.commons.lang3.CharSetUtils;
import org.apache.http.*;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.*;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by JasonY on 2016/12/17.
 */
public class HttpClientUtil {
    private RequestConfig requestConfig;
    private HttpClientConnectionManager connectionManager;
    private HttpClientBuilder httpClientBuilder;
    private String sourceCharset;
    private String targetCharset = "UTF-8";

    public HttpClientUtil() {
        this.requestConfig = RequestConfig.DEFAULT;
        this.connectionManager = new PoolingHttpClientConnectionManager();
        this.httpClientBuilder = HttpClientBuilder.create().setConnectionManager(this.connectionManager);
    }

    public HttpClientUtil(RequestConfig requestConfig, HttpClientConnectionManager connectionManager) {
        this.requestConfig = requestConfig;
        this.connectionManager = connectionManager;
        this.httpClientBuilder = HttpClientBuilder.create().setConnectionManager(this.connectionManager);
    }

    public HttpClientUtil(RequestProperties requestProperties, ConnectionManagerProperties connectionManagerProperties) {
        this.requestConfig = RequestConfig.custom()
                .setExpectContinueEnabled(requestProperties.isExpectContinueEnabled())
                .setCookieSpec(requestProperties.getCookieSpec())
                .setRedirectsEnabled(requestProperties.isRedirectsEnabled())
                .setRelativeRedirectsAllowed(requestProperties.isRelativeRedirectsAllowed())
                .setCircularRedirectsAllowed(requestProperties.isCircularRedirectsAllowed())
                .setMaxRedirects(requestProperties.getMaxRedirects())
                .setAuthenticationEnabled(requestProperties.isAuthenticationEnabled())
                .setConnectionRequestTimeout(requestProperties.getConnectionRequestTimeout())
                .setConnectTimeout(requestProperties.getConnectTimeout())
                .setSocketTimeout(requestProperties.getSocketTimeout())
                .setContentCompressionEnabled(requestProperties.isContentCompressionEnabled())
                .build();

        PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
        if (connectionManagerProperties.getMaxTotal() != 0) {
            connectionManager.setMaxTotal(connectionManagerProperties.getMaxTotal());
        }
        if (connectionManagerProperties.getDefaultMaxPerRoute() != 0) {
            connectionManager.setDefaultMaxPerRoute(connectionManagerProperties.getDefaultMaxPerRoute());
        }
        if (connectionManagerProperties.getMaxPerRoutes() != null && !connectionManagerProperties.getMaxPerRoutes().isEmpty()) {
            for (ConnectionManagerProperties.MaxPerRoute maxPerRoute : connectionManagerProperties.getMaxPerRoutes()) {
                HttpHost httpHost = new HttpHost(maxPerRoute.getHostName(), maxPerRoute.getPort());
                connectionManager.setMaxPerRoute(new HttpRoute(httpHost), maxPerRoute.getMax());
            }
        }
        this.connectionManager = connectionManager;
        this.httpClientBuilder = HttpClientBuilder.create().setConnectionManager(this.connectionManager);
    }

    protected String urlWithQueryString(String url, Map<String, String> params)  {
        try {
            if(params != null){
                URIBuilder uriBuilder = new URIBuilder(url);
                for (String key : params.keySet()){
                    uriBuilder.addParameter(key, params.get(key));
                }

                url = uriBuilder.build().toString();
            }

            return url;
        } catch (URISyntaxException e) {
            throw new HttpClientException("uri syntax exception", e);
        }
    }

    protected HttpGet httpGet(String url, Map<String, String> params) {
        url = urlWithQueryString(url, params);

        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);

        return httpGet;
    }

    public HttpResult doGet(String url, Map<String, String> params) {
        HttpGet httpGet = this.httpGet(url, params);
        return this.doHttpRequest(httpGet);
    }

    public HttpResult doGet(String url) {
        return this.doGet(url, null);
    }

    public void downloadFile(String url, Map<String, String> params, String destFileName) {
        url = urlWithQueryString(url, params);

        CloseableHttpClient httpClient = this.httpClientBuilder.build();
        CloseableHttpResponse response = null;
        HttpGet requst = new HttpGet(url);
        File destFile = new File(destFileName);
        InputStream in = null;
        FileOutputStream fout = null;

        try {
            response = httpClient.execute(requst);

            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                HttpEntity entity = response.getEntity();
                in = entity.getContent();
                fout = new FileOutputStream(destFile);

                int len;
                byte[] tmp = new byte[1024];

                while ((len = in.read(tmp)) != -1) {
                    fout.write(tmp, 0, len);
                }

                fout.flush();
                fout.close();
            }
        } catch (IOException e) {
            throw new HttpClientException("io exception", e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    throw new HttpClientException("occur exception when close inputstream", e);
                }
            }

            if(response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    throw new HttpClientException("occur exception when closing http response", e);
                }
            }
        }
    }

    public void downloadFile(String url, String destFileName) {
        File destFolder = new File(destFileName.substring(0, destFileName.lastIndexOf("/")));
        if (!destFolder.exists()) {
            destFolder.mkdirs();
        }

        downloadFile(url, null, destFileName);
    }

    public HttpResult doPost(String url, Map<String, String> params, Map<String, String> headers) {
        return doPost(url, params, headers, false);
    }

    public HttpResult doPost(String url, Map<String, String> params, Map<String, String> headers, boolean useJson) {
        if (useJson) {
            return doPostUseJsonBody(url, params, headers);
        } else {
            return doPostUseUrlEncoded(url, params, headers);
        }
    }

    protected HttpResult doPostUseJsonBody(String url, Map<String, String> params, Map<String, String> headers) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);

        try {
            if (params != null && !params.isEmpty()) {
                StringEntity bodyEntity = new StringEntity(JSON.toJSONString(params));
                bodyEntity.setContentEncoding("UTF-8");
                bodyEntity.setContentType("application/json");
                httpPost.setEntity(bodyEntity);
            }

            if (headers != null) {
                for (String key : headers.keySet()) {
                    httpPost.addHeader(key, headers.get(key));
                }
            }

            return this.doHttpRequest(httpPost);
        } catch (UnsupportedEncodingException e) {
            throw new HttpClientException("UnsupportedEncodingException", e);
        }
    }

    protected HttpResult doPostUseUrlEncoded(String url, Map<String, String> params, Map<String, String> headers) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);

        if (params != null) {
            List<NameValuePair> postParams = new ArrayList<>();
            for (String key : params.keySet()) {
                postParams.add(new BasicNameValuePair(key, params.get(key)));
            }

            UrlEncodedFormEntity entity = new UrlEncodedFormEntity(postParams, Consts.UTF_8);
            httpPost.setEntity(entity);
        }

        if (headers != null) {
            for (String key : headers.keySet()) {
                httpPost.addHeader(key, headers.get(key));
            }
        }

        return this.doHttpRequest(httpPost);
    }

    public HttpResult doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, params, null);
    }

    public HttpResult doPost(String url) throws IOException {
        return this.doPost(url, null, null);
    }

    protected HttpResult doHttpRequest(HttpRequestBase request) {
        CloseableHttpClient httpClient = this.httpClientBuilder.build();
        CloseableHttpResponse response = null;
        String result = null;
        try {
            response = httpClient.execute(request);

            if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
                result = this.sourceCharset != null ?
                        new String(EntityUtils.toString(response.getEntity()).getBytes(this.sourceCharset), this.targetCharset)
                        :
                        new String(EntityUtils.toString(response.getEntity(), this.targetCharset));
                // result = new String(EntityUtils.toString(response.getEntity()).getBytes("ISO_8859_1"),"UTF-8");

                EntityUtils.consume(response.getEntity());
            }

            return new HttpResult(response.getStatusLine().getStatusCode(), result);
        } catch (IOException e) {
            throw new HttpClientException("http io exception", e);
        } finally {
            if(response != null) {
                try {
                    response.close();
                } catch (IOException e) {
                    throw new HttpClientException("occur exception when closing http response", e);
                }
            }
        }
    }

    public RequestConfig getRequestConfig() {
        return requestConfig;
    }

    public void setRequestConfig(RequestConfig requestConfig) {
        this.requestConfig = requestConfig;
    }

    public class HttpResult {

        private Integer status;
        private String result;

        public HttpResult(){}

        public HttpResult(Integer status, String result){
            this.status = status;
            this.result = result;
        }

        public Integer getStatus() {
            return status;
        }

        public void setStatus(Integer status) {
            this.status = status;
        }

        public String getResult() {
            return result;
        }

        public void setResult(String result) {
            this.result = result;
        }
    }

    public String getSourceCharset() {
        return sourceCharset;
    }

    public void setSourceCharset(String sourceCharset) {
        this.sourceCharset = sourceCharset;
    }

    public String getTargetCharset() {
        return targetCharset;
    }

    public void setTargetCharset(String targetCharset) {
        this.targetCharset = targetCharset;
    }
}
